Custom handles become free, plus invite follow-ups - #126
Merged
Conversation
Custom handles are free now, so AccountSection stops reading the tier entirely. The claim control is offered to everyone and disabled — with the reason — for an unverified email; hiding it is what made the tier gate confusing, and a Business teammate whose own row reads `free` was being told to upgrade while their employer paid. The copy stops naming a plan and says what a custom handle does: it makes you findable outside your teams, and your current handle already works for anyone you give it to. The 402 branch is replaced by the server's 403, so the failure says "verify your email first" instead of "invalid handle". MeResponse gains email_verified; /v1/auth/me has always returned it. It stays null until that call answers so a verified user never sees the "verify your email" line flash.
Sign-in collapsed every failed auth response into one message, so the hardcoded 10/min-per-IP limiter rendered as "Account not found" and sent people off to create a second account. authFailure() branches on the status at all three call sites — both in signInToCloud and the challenge inside login — mapping 429 to a rate-limit message and anything unexpected to the status itself. A knock appeared in the bell twice. Its 8-second toast is archived to history on dismissal and history rows carry no actions, so the panel showed the same knock once actionable in the inbox and once dead below it — and the dead copy is what reads like Join and Decline were lost. ToastEntry.inboxId marks a toast as an echo of an entry that owns the event, and such a toast is dropped instead of archived. The headless container ran as root and left ~30k root-owned files in the mounted worktree. It now builds a user from UID/GID build args; rust moved to /opt so a non-root user can reach it, and CARGO_TARGET_DIR is /target, outside the bind mount — a target dir under vite's root makes the watcher crawl it until the webview never loads. A .dockerignore of "*" stops each build tarring a multi-gigabyte worktree the image never copies from. i18n: dropped the gendered participles the "(а)" and "Invité" forms papered over, and fixed a claim that was stale in all four locales — the people search offered "by name", but display_name is gone and the server matches teammates on email and everyone else on handle.
pnpm records an absolute storeDir in node_modules/.modules.yaml. With the worktree bind-mounted, the container's install stamped /app/.pnpm-store there, and the next command on the host read a storeDir that does not exist locally, decided the tree was foreign and asked to purge it — which fails outside a TTY. That surfaced as every plugin-bundle test failing with a bare "Command failed: pnpm vite build", nowhere near the cause. A named volume at /app/node_modules keeps the two trees apart. Recovery if a container built from the old image poisoned yours: CI=true pnpm install.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pairs with VoltiusApp/server#6 and VoltiusApp/web#7.
Custom handles become free
AccountSectionstops reading the tier entirely. The claim control is offered to everyone and disabled — with the reason — for an unverified email; hiding it is what made the tier gate confusing, and a Business teammate whose own row readsfreewas being told to upgrade while their employer paid.The copy stops naming a plan and says what a custom handle does: it makes you findable outside your teams, and your current handle already works for anyone you give it to. The 402 branch is replaced by the server's 403, so the failure says "verify your email first" instead of "invalid handle".
MeResponsegainsemail_verified—/v1/auth/mehas always returned it, the client just never declared it. It staysnulluntil that call answers, so a verified user never sees the "verify your email" line flash.Gone with the gate:
isFreeTier,isLapsedCustom,meTier,tierKnown, and theupsell/reachableNote/lapsedKeepsHandle/lapsedRenameLocked/errorTierRequiredstrings in four locales.Follow-ups
Sign-in swallowed the rate limiter. Every non-ok response collapsed into one message, so the hardcoded 10/min-per-IP auth limiter rendered as "Account not found" — which sends people off to create a second account they don't need.
authFailure()branches on the status at all three call sites (both insignInToCloud, plus the challenge insidelogin), mapping 429 to a rate-limit message and anything unexpected to the status itself.A knock appeared in the bell twice. Its 8-second toast is archived to history on dismissal, and history rows carry no actions — so the panel showed the same knock once actionable in the inbox and once dead below it, and the dead copy is what reads like Join and Decline were lost.
ToastEntry.inboxIdmarks a toast as an echo of an entry that owns the event; such a toast is dropped instead of archived.The headless container ran as root and left ~30k root-owned files in the mounted worktree. It now builds its user from
UID/GIDbuild args; rust moved to/optso a non-root user can reach it, andCARGO_TARGET_DIRis/target, outside the bind mount — a target dir under vite's root makes the watcher crawl it until the webview never loads. A.dockerignoreof*stops each build tarring a multi-gigabyte worktree the image never copies from.And then it poisoned
node_modules. pnpm records an absolutestoreDirthere; the container stamped/app/.pnpm-store, the host read a path that does not exist locally, decided the tree was foreign and asked to purge it — which fails outside a TTY. That surfaced as every plugin-bundle test failing with a bareCommand failed: pnpm vite build, nowhere near the cause. Avoltius-node-modulesvolume keeps the two trees apart. If your checkout is already in this state:CI=true pnpm install.i18n review of the invite-branch strings: dropped the gendered participles the
(а)andInvitéforms papered over, and fixed a claim that was stale in all four locales — the people search offered "by name", butdisplay_nameis gone and the server matches teammates on email and everyone else on handle. Also unified保管库→保险库in zh.Verification
tsc --noEmitclean.vitest run— 3418 passed, 1 failed of 3419, and the failure is a bareTest timed out in 5000msinTitleBar.syncState, unrelated to any of this. A concurrent session was compiling Rust at load ~16 while it ran; stashing this entire change reproduces the identical failure, and the file passes 2/2 in isolation once the box is quiet.Not proven here:
tauri-drivercoming up on the rebuilt image. The ownership and/targetmechanics were verified live (0 root-owned files after a container run, cargo writing to/targetas the host user), but the cold app build was still going when I stopped it — worth confirminglaunch_app appPath=/target/debug/voltiuson the next UI run.Deploy order
Never before the server. Portal → server → this.